package za.um.td.prototyping;

import za.um.td.entity.level.AbstractTerrain;

import com.jme.bounding.BoundingBox;
import com.jme.entity.Entity;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.scene.Line;
import com.jme.scene.Node;
import com.jme.scene.shape.Quad;
import com.jme.scene.state.MaterialState;
import com.jme.system.DisplaySystem;

/**
 * This class represents a board with blocks and a grid.
 * 
 * @author Etienne Koekemoer
 * 
 */
public class Board extends AbstractTerrain {

	private Vector3f pos; // Position of the board
	private float blockGap; // The size of the gap between blocks
	private Line[] lines; // The lines of the grid
	private BoardBlock blocks[];
	// The number of blocks for the width and height of the board
	private int numBoardBlocksWidth, numBoardBlocksHeight;
	// The sizes of the widths and heights of single blocks
	private float blockWidth, blockHeight;
	private ColorRGBA lineColour; // The colour of the lines
	private float lineWidth; // The width of the lines
	private Node boardSpatial;

	// These variables are used in most calculations and stored in memory for
	// optimisation
	private float numBoardBlocksWidthDiv2;
	private float numBoardBlocksHeightDiv2;
	private float blockHeightPlusBlockGap;
	private float blockWidthPlusBlockGap;

	// Default board values
	public static final Vector3f DEFAULT_POS = new Vector3f();
	public static final int DEFAULT_NUM_BOARD_BLOCKS_WIDTH = 25;
	public static final int DEFAULT_NUM_BOARD_BLOCKS_HEIGHT = 25;
	public static final float DEFAULT_BLOCK_WIDTH = 1.0f;
	public static final float DEFAULT_BLOCK_HEIGHT = 1.0f;
	public static final float DEFAULT_BLOCK_GAP = 0.1f;
	public static final ColorRGBA DEFAULT_LINE_COLOUR = ColorRGBA.white;
	public static final float LINE_WIDTH = 1.1f;

	/**
	 * @param id
	 *            The ID of the board Constructs a default board. See the
	 *            DEFAULT_* constants of this class.
	 */
	public Board(String id) {
		super(id);
		
		boardSpatial = new Node();
		setSpatial(boardSpatial);
		
		initMembers(DEFAULT_POS, DEFAULT_NUM_BOARD_BLOCKS_WIDTH,
				DEFAULT_NUM_BOARD_BLOCKS_HEIGHT, DEFAULT_BLOCK_WIDTH,
				DEFAULT_BLOCK_HEIGHT, DEFAULT_BLOCK_GAP, DEFAULT_LINE_COLOUR,
				LINE_WIDTH);
		//initLines();
		initBlocks();
		
		//this.attachLinesToNode(boardSpatial);
		
		getSpatial().lock();
	}

	/**
	 * Constructor
	 * 
	 * @param id
	 *            The ID of the board
	 * @param pos
	 *            The position of the board
	 * @param numBoardBlocksWidth
	 *            The number of blocks for the width of the board
	 * @param numBoardBlocksHeight
	 *            The number of blocks for the height of the board
	 * @param blockWidth
	 *            The sizes of the widths single blocks
	 * @param blockHeight
	 *            The sizes of the heights of single blocks
	 * @param blocksGap
	 *            The size of the gap between blocks
	 * @param lineColour
	 *            The colour of the lines
	 * @param lineWidth
	 *            The width of the lines
	 */
	public Board(String id, Vector3f pos, int numBoardBlocksWidth,
			int numBoardBlocksHeight, float blockWidth, float blockHeight,
			float blocksGap, ColorRGBA lineColour, float lineWidth) {
		super(id);
		
		boardSpatial = new Node();
		setSpatial(boardSpatial);
		
		initMembers(pos, numBoardBlocksWidth, numBoardBlocksHeight, blockWidth,
				blockHeight, blocksGap, lineColour, lineWidth);
		//initLines();
		initBlocks();
		
		//this.attachLinesToNode(boardSpatial);
		
		getSpatial().lock();
	}

	/*
	 * Initialise member variables
	 */
	private void initMembers(Vector3f pos, int numBoardBlocksWidth,
			int numBoardBlocksHeight, float blockWidth, float blockHeight,
			float blocksGap, ColorRGBA lineColour, float lineWidth) {
		this.pos = pos;

		this.numBoardBlocksWidth = numBoardBlocksWidth;
		this.numBoardBlocksHeight = numBoardBlocksHeight;

		this.blockWidth = blockWidth;
		this.blockHeight = blockHeight;

		this.blockGap = blocksGap;

		this.lineColour = lineColour;
		this.lineWidth = lineWidth;

		// Calculations are stored in memory for optimisation
		numBoardBlocksWidthDiv2 = (float) numBoardBlocksWidth * 0.5f;
		numBoardBlocksHeightDiv2 = (float) numBoardBlocksHeight * 0.5f;
		blockHeightPlusBlockGap = blockHeight + blockGap;
		blockWidthPlusBlockGap = blockWidth + blockGap;

		lines = new Line[numBoardBlocksHeight + numBoardBlocksWidth + 2];
		blocks = new BoardBlock[numBoardBlocksWidth * numBoardBlocksHeight];
	}
	
	/*
	 * 
	 */
	private void initBlocks() {
		for (int x = 0; x < numBoardBlocksWidth; ++x) {
			for (int y = 0; y < numBoardBlocksHeight; ++y) {
				((Quad)createBlock(x, y, (Node)getSpatial()).getSpatial()).setRandomColors();
			}
		}
	}

	/*
	 * Initialise the lines of the grid on the board
	 */
	private void initLines() {
		// A bunch of calculations being used in the for loops over and over
		float blockGapDiv2 = blockGap * 0.5f;
		float boardWidth = blockWidthPlusBlockGap * numBoardBlocksWidth
				+ blockGap;
		float boardWidthDiv2 = boardWidth * 0.5f;
		float boardHeight = blockHeightPlusBlockGap * numBoardBlocksHeight
				+ blockGap;
		float boardHeightDiv2 = boardHeight * 0.5f;

		// The horizontal lines
		for (int i = 0; i < numBoardBlocksHeight + 1; ++i) {
			Vector3f[] linePoints = new Vector3f[2];

			linePoints[0] = new Vector3f(pos);
			linePoints[1] = new Vector3f(pos);

			linePoints[0].x -= boardWidthDiv2 - blockGapDiv2;
			linePoints[1].x += boardWidthDiv2 - blockGapDiv2;

			linePoints[0].z += ((i - numBoardBlocksHeightDiv2) * blockHeightPlusBlockGap);
			linePoints[1].z = linePoints[0].z;

			lines[i] = new Line("BoardLine" + i + "x", linePoints, null, null,
					null);
		}

		// The vertical lines
		for (int i = 0; i < numBoardBlocksWidth + 1; ++i) {
			Vector3f[] linePoints = new Vector3f[2];

			linePoints[0] = new Vector3f(pos);
			linePoints[1] = new Vector3f(pos);

			linePoints[0].z -= boardHeightDiv2 - blockGapDiv2;
			linePoints[1].z += boardHeightDiv2 - blockGapDiv2;

			linePoints[0].x += ((i - numBoardBlocksWidthDiv2) * blockWidthPlusBlockGap);
			linePoints[1].x = linePoints[0].x;

			lines[numBoardBlocksHeight + 1 + i] = new Line("BoardLine" + i
					+ "x", linePoints, null, null, null);
		}

		// Line colour
		MaterialState ms = DisplaySystem.getDisplaySystem().getRenderer()
				.createMaterialState();
		ms.setAmbient(lineColour);
		ms.setEmissive(lineColour);
		ms.setDiffuse(lineColour);
		ms.setSpecular(lineColour);
		ms.setShininess(0);

		// Set some line parameters for all the lines
		for (int i = 0; i < lines.length; ++i) {
			Line line = lines[i];
			line.setRenderState(ms);
			line.setLineWidth(lineWidth);
			line.setModelBound(new BoundingBox());
			line.updateModelBound();
		}
	}

	/**
	 * Attach the grid lines to the specified node
	 * 
	 * @param node
	 *            Attach the grid lines to this node
	 */
	public void attachLinesToNode(Node node) {
		for (int i = 0; i < lines.length; ++i) {
			node.attachChild(lines[i]);
		}
	}

	/**
	 * Update the render states of all the lines
	 */
	public void updateLinesRenderStates() {
		for (int i = 0; i < lines.length; ++i) {
			lines[i].updateRenderState();
		}
	}

	/**
	 * Create a block at the given X and Y coordinates on the grid. The top-left
	 * block of the grid is 0,0. The block just right to 0,0 will be 1,0. The
	 * block just below 0,0 will be 0,1.
	 * 
	 * @param x
	 *            The X coordinate of the block on the grid
	 * @param y
	 *            The Y coordinate of the block on the grid
	 * @return The newly created block
	 */
	public BoardBlock createBlock(int x, int y) {
		Vector3f blockPos = new Vector3f(pos);
		blockPos.x = blockWidthPlusBlockGap
				* (x - numBoardBlocksWidthDiv2 + 0.5f);
		blockPos.z = blockHeightPlusBlockGap
				* (y - numBoardBlocksHeightDiv2 + 0.5f);
		blocks[y * numBoardBlocksWidth + x] = new BoardBlock("BoardBlock" + x
				+ "x" + y, blockPos, blockWidth, blockHeight);
		return blocks[y * numBoardBlocksWidth + x];
	}

	/**
	 * This method does the same as <code>createBlock(int x, int y)</code>, but
	 * it also attaches the block to the specified node
	 * 
	 * @see createBlock(int x, int y)
	 * @param x
	 *            The X coordinate of the block on the grid
	 * @param y
	 *            The Y coordinate of the block on the grid
	 * @param node
	 *            The node that the new block will be attached to
	 * @return The newly created block
	 */
	public BoardBlock createBlock(int x, int y, Node node) {
		BoardBlock block = createBlock(x, y);
		node.attachChild(block.getSpatial());
		return block;
	}

	/**
	 * Removes the board block at the spesified grid coordinates and detaches
	 * itself from the specified node
	 * 
	 * @param x The X coordinate of the block to remove on the grid
	 * @param y The Y coordinate of the block to remove on the grid
	 * @param node The node to detach the block from
	 * @return the removed block
	 */
	public BoardBlock removeBlock(int x, int y, Node node) {
		BoardBlock toRemove = blocks[y * numBoardBlocksWidth + x];
		node.detachChild(toRemove.getSpatial());
		blocks[y * numBoardBlocksWidth + x] = null;
		return toRemove;
	}

	/**
	 * Return the board block at the specified grid coordinates
	 * 
	 * @param x
	 *            The X coordinate of the block on the grid
	 * @param y
	 *            The Y coordinate of the block on the grid
	 * @return the board block at the specified grid coordinate
	 */
	public BoardBlock getBlock(int x, int y) {
		return blocks[y * numBoardBlocksWidth + x];
	}

	@Override
	public float getHeight(Vector3f location) {
		return 0;
	}

	@Override
	public int getNumBlocksX() {
		return numBoardBlocksWidth;
	}

	@Override
	public int getNumBlocksZ() {
		return numBoardBlocksHeight;
	}

	@Override
	public float getXLength() {
		return (getNumBlocksX() * blockWidthPlusBlockGap) + blockGap;
	}

	@Override
	public float getZLength() {
		return (getNumBlocksZ() * blockHeightPlusBlockGap) + blockGap;
	}

	@Override
	public Vector3f getCentreLocation() {
		return pos;
	}

	@Override
	public Vector3f getScale() {
		return Vector3f.UNIT_XYZ;
	}
	
}
